package com.vzaar.uploader
{
	import flash.events.DataEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.FileReference;
	import flash.net.URLRequest;
	import flash.net.URLRequestHeader;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;
	import flash.system.Security;
	import flash.xml.XMLDocument;
	import flash.xml.XMLNode;

	public class S3Uploader extends EventDispatcher
	{
		[Event(name="open", type="flash.events.Event.OPEN")]
		[Event(name="uploadCompleteData", type="flash.events.DataEvent.UPLOAD_COMPLETE_DATA")]
		[Event(name="ioError", type="flash.events.IOErrorEvent.IO_ERROR")]
		[Event(name="securityError", type="flash.events.SecurityErrorEvent.SECURITY_ERROR")]
		[Event(name="progress", type="flash.events.ProgressEvent.PROGRESS")]

		private static const ENDPOINT:String = ".s3.amazonaws.com";
		private var URL:String;
		public var fileReference:FileReference;
		private var options:S3UploadOptions;

		private var accessKeyId:String;
		private var key:String;
		private var bucket:String;
		private var expDate:String;

		public function S3Uploader(accessKeyID:String, key:String, bucket:String, expDate:String, uploadOption:S3UploadOptions = null)
		{
			this.accessKeyId = accessKeyID;
			this.key = key;
			this.bucket = bucket;
			this.expDate = expDate;
			this.options = uploadOption;

			URL = "http://" + bucket + ENDPOINT;
		}

		private function buildVariable(file:FileReference):URLVariables
		{
			var data:URLVariables = new URLVariables();

			data.key = key;
			data.acl = options.acl;
			data.AWSAccessKeyId = accessKeyId;
			data.policy = options.policy;
			data.signature = options.signature;
			data["Content-Type"] = options.contentType;
			data.success_action_status = "201";
			//data.filename = file.name;

			return data;
		}

		public function upload(file:FileReference):void
		{
			var urlReq:URLRequest = new URLRequest(URL);
			//urlReq.contentType = "multipart/form-data";
			urlReq.method = URLRequestMethod.POST;
			urlReq.data = buildVariable(file);

			var headerUserAgent:URLRequestHeader = new URLRequestHeader('User-Agent', 'Vzaar API Client');
			var headerACL:URLRequestHeader = new URLRequestHeader('x-amz-acl', options.acl);
			var headerEnclosure:URLRequestHeader = new URLRequestHeader('Enclosure-Type', 'multipart/form-data');

			urlReq.requestHeaders.push(headerUserAgent);
			urlReq.requestHeaders.push(headerEnclosure);
			urlReq.requestHeaders.push(headerACL);

			Security.loadPolicyFile(URL + "/crossdomain.xml");
			// set up event handlers *****************************************************
			file.addEventListener(Event.OPEN, onOpen);
			file.addEventListener(ProgressEvent.PROGRESS, onProgress);
			file.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			file.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
			file.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onUploadCompleteData);
			file.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHttpStatus);
			// *****************************************************************************

			fileReference = file;
			file.upload(urlReq, "file", false);
		}

		private function onOpen(event:Event):void
		{
			dispatchEvent(event);
		}

		private function onIOError(event:IOErrorEvent):void
		{
			/*
			 * FileReference.upload likes to send cryptic IOErrors when it doesn't get a status code that it likes.
			 * If we already got an error HTTP status code, don't propagate this event since the HTTPStatusEvent
			 * event handler dispatches an IOErrorEvent.
			 */
			this.dispatchEvent(event);
		}

		private function onSecurityError(event:SecurityErrorEvent):void
		{
			this.dispatchEvent(event);
		}

		private function onProgress(event:ProgressEvent):void
		{
			this.dispatchEvent(event);
		}

		private function onUploadCompleteData(event:DataEvent):void
		{
			var data:String = event.data;
			if (isError(data))
			{
				this.dispatchEvent(
						new IOErrorEvent(IOErrorEvent.IO_ERROR, event.bubbles, event.cancelable, "Amazon S3 returned an error: " + data + ".")
				);
			} else
			{
				this.dispatchEvent(new DataEvent(DataEvent.UPLOAD_COMPLETE_DATA, event.bubbles, event.cancelable, data));
			}
		}

		public function isError(responseText:String):Boolean
		{
			var xml:XMLDocument = new XMLDocument();
			xml.ignoreWhite = true;
			xml.parseXML(responseText);
			var root:XMLNode = xml.firstChild;
			return !(root == null || root.nodeName != "Error");

		}

		/**
		 * Statuses 200 and 201 are acceptable for Amazon S3, in rest of the cases consider we have a problem
		 * @param event
		 */
		private function onHttpStatus(event:HTTPStatusEvent):void
		{
			if (event.status == 200 || event.status == 201)
			{
				this.dispatchEvent(new DataEvent(DataEvent.UPLOAD_COMPLETE_DATA, event.bubbles, event.cancelable, "Amazon S3 returned HTTP status " + event.status.toString() + "."));
			} else
			{
				this.dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, event.bubbles, event.cancelable, "Amazon S3 returned an error: HTTP status " + event.status.toString() + "."));
			}
		}
	}
}